/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.linkoog.devtools.utils;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterators;
import java.util.StringTokenizer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;


public abstract class StringUtils {

	/**
	 * 去掉指定前缀
	 *
	 * @param str    字符串
	 * @param prefix 前缀
	 * @return 切掉后的字符串，若前缀不是 preffix， 返回原字符串
	 */
	public static String removePrefix(CharSequence str, CharSequence prefix) {
		if (isEmpty(str) || isEmpty(prefix)) {
			return str(str);
		}

		final String str2 = str.toString();
		if (str2.startsWith(prefix.toString())) {
			return subSuf(str2, prefix.length());// 截取后半段
		}
		return str2;
	}

	/**
	 * 去掉指定后缀
	 *
	 * @param str    字符串
	 * @param suffix 后缀
	 * @return 切掉后的字符串，若后缀不是 suffix， 返回原字符串
	 */
	public static String removeSuffix(CharSequence str, CharSequence suffix) {
		if (isEmpty(str) || isEmpty(suffix)) {
			return str(str);
		}

		final String str2 = str.toString();
		if (str2.endsWith(suffix.toString())) {
			return subPre(str2, str2.length() - suffix.length());// 截取前半段
		}
		return str2;
	}

	/**
	 * {@link CharSequence} 转为字符串，null安全
	 *
	 * @param cs {@link CharSequence}
	 * @return 字符串
	 */
	public static String str(CharSequence cs) {
		return null == cs ? null : cs.toString();
	}

	/**
	 * 切割指定位置之前部分的字符串
	 *
	 * @param string         字符串
	 * @param toIndexExclude 切割到的位置（不包括）
	 * @return 切割后的剩余的前半部分字符串
	 */
	public static String subPre(CharSequence string, int toIndexExclude) {
		return sub(string, 0, toIndexExclude);
	}

	/**
	 * 切割指定位置之后部分的字符串
	 *
	 * @param string    字符串
	 * @param fromIndex 切割开始的位置（包括）
	 * @return 切割后后剩余的后半部分字符串
	 */
	public static String subSuf(CharSequence string, int fromIndex) {
		if (isEmpty(string)) {
			return null;
		}
		return sub(string, fromIndex, string.length());
	}

	/**
	 * 改进JDK subString<br>
	 * index从0开始计算，最后一个字符为-1<br>
	 * 如果from和to位置一样，返回 "" <br>
	 * 如果from或to为负数，则按照length从后向前数位置，如果绝对值大于字符串长度，则from归到0，to归到length<br>
	 * 如果经过修正的index中from大于to，则互换from和to example: <br>
	 * abcdefgh 2 3 =》 c <br>
	 * abcdefgh 2 -3 =》 cde <br>
	 *
	 * @param str              String
	 * @param fromIndexInclude 开始的index（包括）
	 * @param toIndexExclude   结束的index（不包括）
	 * @return 字串
	 */
	public static String sub(CharSequence str, int fromIndexInclude, int toIndexExclude) {
		if (isEmpty(str)) {
			return str(str);
		}
		int len = str.length();

		if (fromIndexInclude < 0) {
			fromIndexInclude = len + fromIndexInclude;
			if (fromIndexInclude < 0) {
				fromIndexInclude = 0;
			}
		} else if (fromIndexInclude > len) {
			fromIndexInclude = len;
		}

		if (toIndexExclude < 0) {
			toIndexExclude = len + toIndexExclude;
			if (toIndexExclude < 0) {
				toIndexExclude = len;
			}
		} else if (toIndexExclude > len) {
			toIndexExclude = len;
		}

		if (toIndexExclude < fromIndexInclude) {
			int tmp = fromIndexInclude;
			fromIndexInclude = toIndexExclude;
			toIndexExclude = tmp;
		}

		if (fromIndexInclude == toIndexExclude) {
			return "";
		}

		return str.toString().substring(fromIndexInclude, toIndexExclude);
	}

	/**
	 * <p>字符串是否为空，空的定义如下：</p>
	 * <ol>
	 *     <li>{@code null}</li>
	 *     <li>空字符串：{@code ""}</li>
	 * </ol>
	 *
	 * <p>例：</p>
	 * <ul>
	 *     <li>{@code StrUtil.isEmpty(null)     // true}</li>
	 *     <li>{@code StrUtil.isEmpty("")       // true}</li>
	 *     <li>{@code StrUtil.isEmpty(" \t\n")  // false}</li>
	 *     <li>{@code StrUtil.isEmpty("abc")    // false}</li>
	 * </ul>
	 *
	 * <p>注意：该方法与 {@link #isBlank(CharSequence)} 的区别是：该方法不校验空白字符。</p>
	 * <p>建议：</p>
	 * <ul>
	 *     <li>该方法建议用于工具类或任何可以预期的方法参数的校验中。</li>
	 *     <li>需要同时校验多个字符串时，建议采用 </li>
	 * </ul>
	 *
	 * @param str 被检测的字符串
	 * @return 是否为空
	 * @see #isBlank(CharSequence)
	 */
	public static boolean isEmpty(CharSequence str) {
		return str == null || str.length() == 0;
	}

	/**
	 * <p>字符串是否为非空白，非空白的定义如下： </p>
	 * <ol>
	 *     <li>不为 {@code null}</li>
	 *     <li>不为空字符串：{@code ""}</li>
	 * </ol>
	 *
	 * <p>例：</p>
	 * <ul>
	 *     <li>{@code StrUtil.isNotEmpty(null)     // false}</li>
	 *     <li>{@code StrUtil.isNotEmpty("")       // false}</li>
	 *     <li>{@code StrUtil.isNotEmpty(" \t\n")  // true}</li>
	 *     <li>{@code StrUtil.isNotEmpty("abc")    // true}</li>
	 * </ul>
	 *
	 * <p>注意：该方法与 {@link #isNotBlank(CharSequence)} 的区别是：该方法不校验空白字符。</p>
	 * <p>建议：该方法建议用于工具类或任何可以预期的方法参数的校验中。</p>
	 *
	 * @param str 被检测的字符串
	 * @return 是否为非空
	 * @see #isEmpty(CharSequence)
	 */
	public static boolean isNotEmpty(CharSequence str) {
		return false == isEmpty(str);
	}

	public static boolean isBlank(CharSequence str) {
		int strLen;
		if (str != null && (strLen = str.length()) != 0) {
			for(int i = 0; i < strLen; ++i) {
				if (!Character.isWhitespace(str.charAt(i))) {
					return false;
				}
			}

			return true;
		} else {
			return true;
		}
	}

	public static boolean isNotBlank(CharSequence str) {
		return !isBlank(str);
	}

	/**
	 * Check that the given {@code CharSequence} is neither {@code null} nor
	 * of length 0.
	 * <p>Note: this method returns {@code true} for a {@code CharSequence}
	 * that purely consists of whitespace.
	 * <p><pre class="code">
	 * StringUtils.hasLength(null) = false
	 * StringUtils.hasLength("") = false
	 * StringUtils.hasLength(" ") = true
	 * StringUtils.hasLength("Hello") = true
	 * </pre>
	 * @param str the {@code CharSequence} to check (may be {@code null})
	 * @return {@code true} if the {@code CharSequence} is not {@code null} and has length
	 */
	public static boolean hasLength(CharSequence str) {
		return (str != null && str.length() > 0);
	}

	/**
	 * Copy the given {@code Collection} into a {@code String} array.
	 * <p>The {@code Collection} must contain {@code String} elements only.
	 * @param collection the {@code Collection} to copy
	 * @return the {@code String} array ({@code null} if the supplied
	 * {@code Collection} was {@code null})
	 */
	public static String[] toStringArray(Collection<String> collection) {
		if (collection == null) {
			return null;
		}

		return collection.toArray(new String[collection.size()]);
	}
	/**
	 * Tokenize the given {@code String} into a {@code String} array via a
	 * {@link StringTokenizer}.
	 * <p>The given {@code delimiters} string can consist of any number of
	 * delimiter characters. Each of those characters can be used to separate
	 * tokens. A delimiter is always a single character; for multi-character
	 * delimiters, consider using
	 * @param str the {@code String} to tokenize
	 * @param delimiters the delimiter characters, assembled as a {@code String}
	 * (each of the characters is individually considered as a delimiter)
	 * @param trimTokens trim the tokens via {@link String#trim()}
	 * @param ignoreEmptyTokens omit empty tokens from the result array
	 * (only applies to tokens that are empty after trimming; StringTokenizer
	 * will not consider subsequent delimiters as token in the first place).
	 * @return an array of the tokens ({@code null} if the input {@code String}
	 * was {@code null})
	 * @see StringTokenizer
	 * @see String#trim()
	 */
	public static String[] tokenizeToStringArray(
			String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

		if (str == null) {
			return null;
		}

		StringTokenizer st = new StringTokenizer(str, delimiters);
		List<String> tokens = new ArrayList<String>();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (trimTokens) {
				token = token.trim();
			}
			if (!ignoreEmptyTokens || token.length() > 0) {
				tokens.add(token);
			}
		}
		return toStringArray(tokens);
	}

	public static String join(boolean[] array, char delimiter) {
		return array == null ? null : join((boolean[])array, delimiter, 0, array.length);
	}

	public static String join(boolean[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder(array.length * 5 + array.length - 1);

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(byte[] array, char delimiter) {
		return array == null ? null : join((byte[])array, delimiter, 0, array.length);
	}

	public static String join(byte[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(char[] array, char delimiter) {
		return array == null ? null : join((char[])array, delimiter, 0, array.length);
	}

	public static String join(char[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder(array.length * 2 - 1);

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(double[] array, char delimiter) {
		return array == null ? null : join((double[])array, delimiter, 0, array.length);
	}

	public static String join(double[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(float[] array, char delimiter) {
		return array == null ? null : join((float[])array, delimiter, 0, array.length);
	}

	public static String join(float[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(int[] array, char separator) {
		return array == null ? null : join((int[])array, separator, 0, array.length);
	}

	public static String join(int[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	public static String join(Iterable<?> iterable, char separator) {
		return iterable != null ? join(iterable.iterator(), separator) : null;
	}

	public static String join(Iterable<?> iterable, String separator) {
		return iterable != null ? join(iterable.iterator(), separator) : null;
	}

	public static String join(Iterator<?> iterator, char separator) {
		if (iterator == null) {
			return null;
		} else {
			return !iterator.hasNext() ? "" : (String) streamsOf(iterator).collect(LangCollectors.joining(toStringOrEmpty(String.valueOf(separator)), "", "", StringUtils::toStringOrEmpty));
		}
	}

	public static <E> Stream<E> streamsOf(Collection<E> collection) {
		return collection == null ? Stream.empty() : collection.stream();
	}

	public static <E> Stream<E> streamsOf(Iterator<E> iterator) {
		return iterator == null ? Stream.empty() : StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 16), false);
	}

	public static <T> Stream<T> streamsOf(T... values) {
		return values == null ? Stream.empty() : Stream.of(values);
	}

	public static String join(Iterator<?> iterator, String separator) {
		if (iterator == null) {
			return null;
		} else {
			return !iterator.hasNext() ? "" : (String)streamsOf(iterator).collect(LangCollectors.joining(toStringOrEmpty(separator), "", "", StringUtils::toStringOrEmpty));
		}
	}

	public static String join(List<?> list, char separator, int startIndex, int endIndex) {
		if (list == null) {
			return null;
		} else {
			int noOfItems = endIndex - startIndex;
			if (noOfItems <= 0) {
				return "";
			} else {
				List<?> subList = list.subList(startIndex, endIndex);
				return join(subList.iterator(), separator);
			}
		}
	}

	public static String join(List<?> list, String separator, int startIndex, int endIndex) {
		if (list == null) {
			return null;
		} else {
			int noOfItems = endIndex - startIndex;
			if (noOfItems <= 0) {
				return "";
			} else {
				List<?> subList = list.subList(startIndex, endIndex);
				return join(subList.iterator(), separator);
			}
		}
	}

	public static String join(long[] array, char separator) {
		return array == null ? null : join((long[])array, separator, 0, array.length);
	}

	public static String join(long[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	private static String toStringOrEmpty(Object obj) {
		return Objects.toString(obj, "");
	}

	public static String join(Object[] array, char delimiter) {
		return array == null ? null : join((Object[])array, delimiter, 0, array.length);
	}

	public static String join(Object[] array, char delimiter, int startIndex, int endIndex) {
		return join(array, String.valueOf(delimiter), startIndex, endIndex);
	}

	public static String join(Object[] array, String delimiter) {
		return array != null ? join((Object[])array, toStringOrEmpty(delimiter), 0, array.length) : null;
	}

	public static String join(Object[] array, String delimiter, int startIndex, int endIndex) {
		return array != null ? (String)streamsOf(array).skip((long)startIndex).limit((long)Math.max(0, endIndex - startIndex)).collect(LangCollectors.joining(delimiter, "", "", StringUtils::toStringOrEmpty)) : null;
	}

	public static String join(short[] array, char delimiter) {
		return array == null ? null : join((short[])array, delimiter, 0, array.length);
	}

	public static String join(short[] array, char delimiter, int startIndex, int endIndex) {
		if (array == null) {
			return null;
		} else if (endIndex - startIndex <= 0) {
			return "";
		} else {
			StringBuilder stringBuilder = new StringBuilder();

			for(int i = startIndex; i < endIndex; ++i) {
				stringBuilder.append(array[i]).append(delimiter);
			}

			return stringBuilder.substring(0, stringBuilder.length() - 1);
		}
	}

	@SafeVarargs
	public static <T> String join(T... elements) {
		return join((Object[])elements, (String)null);
	}

	public static String joinWith(String delimiter, Object... array) {
		if (array == null) {
			throw new IllegalArgumentException("Object varargs must not be null");
		} else {
			return join(array, delimiter);
		}
	}


}
